package rubik;

import java.util.ArrayList;
import java.util.Random;

import rubik.seqs.Sequence;

public class Cube {

	public static final int ORIG_SIZE = 3;
	public static final int SIZE = 3 + 2;

	protected Object[][][] _cube;
	protected Object[][][] _copyCube;

	public Cube() {
		this._cube = new Object[SIZE][SIZE][SIZE];
		this._copyCube = new Object[SIZE][SIZE][SIZE];

		for (int i = 1; i > -2; i--) {
			for (int j = -1; j < 2; j++) {
				for (EFace eface : EFace.values()) {
					this.setInFace(eface, j, i, eface.color());
				}
			}
		}
		this.save();
	}

	protected Object get(int x, int y, int z) {
		return this._cube[x + 2][y + 2][z + 2];
	}

	protected void set(int x, int y, int z, Object val) {
		this._cube[x + 2][y + 2][z + 2] = val;
	}

	public Object[][] getFace(EFace eface) {
		Object[][] res = new Object[ORIG_SIZE][ORIG_SIZE];

		for (int i = -1; i < 2; i++) {
			for (int j = 1; j > -2; j--) {
				res[i + 1][j + 1] = this.getFromFace(eface, i, j);
			}
		}
		return res;

	}

	protected Object getFromFaceX(EFace eface, int i, int j, int x) {
		switch (eface) {
		case UP:
			return this.get(i, x, j);
		case DOWN:
			return this.get(i, -x, j);
		case RIGHT:
			return this.get(x, i, j);
		case LEFT:
			return this.get(-x, i, j);
		case FRONT:
			return this.get(i, j, -x);
		case BACK:
			return this.get(i, j, x);
		}
		return 0;
	}

	protected Object getFromFace(EFace eface, int i, int j) {
		return this.getFromFaceX(eface, i, j, 2);
	}

	protected Object getFromFaceEdge(EFace eface, int i, int j) {
		return this.getFromFaceX(eface, i, j, 1);
	}

	protected void setInFaceX(EFace eface, int i, int j, Object val, int x) {
		switch (eface) {
		case UP:
			this.set(i, x, j, val);
			return;
		case DOWN:
			this.set(i, -x, j, val);
			return;
		case RIGHT:
			this.set(x, i, j, val);
			return;
		case LEFT:
			this.set(-x, i, j, val);
			return;
		case FRONT:
			this.set(i, j, -x, val);
			return;
		case BACK:
			this.set(i, j, x, val);
			return;
		}
	}

	protected void setInFace(EFace eface, int i, int j, Object val) {
		this.setInFaceX(eface, i, j, val, 2);
	}

	protected void setInFaceEdge(EFace eface, int i, int j, Object val) {
		this.setInFaceX(eface, i, j, val, 1);
	}

	protected Object[][] getFaceEdge(EFace eface) {
		Object[][] res = new Object[SIZE][SIZE];

		for (int i = -2; i < 3; i++) {
			for (int j = 2; j > -3; j--) {
				res[i + 2][j + 2] = this.getFromFaceEdge(eface, i, j);
			}
		}
		return res;
	}

	public void turncw(EFace eface) {
		this.turn(eface, false);
	}

	public void turnccw(EFace eface) {
		this.turn(eface, true);
	}

	public void turn(EFace eface, boolean ccw) {

		// turn the face
		Object[][] face = this.getFace(eface);
		for (int i = 0; i < ORIG_SIZE; i++) {
			for (int j = 0; j < ORIG_SIZE; j++) {
				int x = i - 1;
				int y = j - 1;
				this.setInFace(eface, y, -x, face[i][j]);
			}
		}

		// turn the adjacent cells on other faces
		Object[][] edges = this.getFaceEdge(eface);
		for (int i = 0; i < SIZE; i++) {
			for (int j = 0; j < SIZE; j++) {
				int x = i - 2;
				int y = j - 2;
				if (ccw) {
					x = -x;
					y = -y;
				}
				switch (eface) {
				case FRONT:
				case LEFT:
				case UP:
					this.setInFaceEdge(eface, y, -x, edges[i][j]);
					break;
				case BACK:
				case RIGHT:
				case DOWN:
					this.setInFaceEdge(eface, -y, x, edges[i][j]);
					break;
				}
			}
		}
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();

		// back
		for (int i = -1; i < 2; i++) {
			for (int j = 0; j < ORIG_SIZE; j++) {
				sb.append("\t");
			}
			for (int j = -1; j < 2; j++) {
				sb.append(this.getFromFace(EFace.BACK, j, i) + "\t");
			}
			sb.append("\n");
		}

		for (int i = 1; i > -2; i--) {

			// left, up
			EFace[] faces = new EFace[] { EFace.LEFT, EFace.UP };
			for (EFace eface : faces) {
				for (int j = -1; j < 2; j++) {
					sb.append(this.getFromFace(eface, j, i) + "\t");
				}
			}

			// right, down
			faces = new EFace[] { EFace.RIGHT, EFace.DOWN };
			for (EFace eface : faces) {
				for (int j = 1; j > -2; j--) {
					sb.append(this.getFromFace(eface, j, i) + "\t");
				}
			}
			sb.append("\n");
		}

		// front
		for (int i = 1; i > -2; i--) {
			for (int j = 0; j < ORIG_SIZE; j++) {
				sb.append("\t");
			}
			for (int j = -1; j < 2; j++) {
				sb.append(this.getFromFace(EFace.FRONT, j, i) + "\t");
			}
			sb.append("\n");
		}
		return sb.toString();

	}
	
	
	protected static void copyCube (Object[][][] from, Object[][][] to)
	{
		for (int i = 0; i < from.length; i++) {
			for (int j = 0; j < from[i].length; j++) {
				for (int j2 = 0; j2 < from[i][j].length; j2++) {
					to[i][j][j2] = from[i][j][j2];
				}
			}
		}
	}
	public void save()
	{
		copyCube(this._cube,this._copyCube);
	}
	
	public void reset()
	{
		copyCube(this._copyCube, this._cube);
	}
	
	public void applySeq(ArrayList<EMove> seq)
	{
		for (int i = 0; i < seq.size(); i++) {
			EMove move = seq.get(i);
			this.turn(move.getFace(), move.isCCW());
		}
	}
	
	public void applySeq(Sequence seq)
	{
		this.applySeq(seq.getMoves());
	}

	public int countConflicts() {
		int conflicts = 0;
		for (EFace eface: EFace.values())
		{
			Object center = this.getFromFace(eface, 0, 0);
			for (int i = -1; i < 2; i++) {
				for (int j = -1; j < 2; j++)
				{
					Object x = this.getFromFace(eface, i, j); 
					if (!x.equals(center))
						conflicts++;
				}
			}
		}
		return conflicts;
	}
	
	public int countCorrectEdges()
	{
		int c = 0;
		int[] edge = new int[3];
		int[] vals = new int[] {-1,1};
		for (int fi = 0; fi < 3; fi++)
		{
			for (int fs = -1; fs < 2; fs += 2)
			{
				edge[fi] = 2*fs;
				for (int i = 0; i < vals.length; i++) {
					
					edge[(fi+1)%3] = vals[i];
					edge[(fi+2)%3] = 0;
					
					if (checkEdge(edge))
						c++;
					
					edge[(fi+1)%3] = 0;
					edge[(fi+2)%3] = vals[i];
					
					if (checkEdge(edge))
						c++;
					
				}
			}
			
		}
		return c/2;
	}

	private boolean checkEdge(int[] edge) {
		if (this.biasedGet(edge).equals(this.biasedGet(edgeCenter(edge))))
		{
			int[] e = adjacentEdge(edge);
			if (this.biasedGet(e).equals(this.biasedGet(edgeCenter(e))))
				return true;
		}
		return false;
	}
	
	private int[] edgeCenter(int[] edge)
	{
		int[] center = new int[3];
		for (int i = 0; i < center.length; i++) {
			if (Math.abs(edge[i]) == 2)
				center[i] = edge[i];
			else
				center[i] = 0;
		}
		return center;
	}
	private static int[] adjacentEdge(int[] edge)
	{
		int[] e = new int[3];
		for (int i = 0; i < edge.length; i++) {
			switch (edge[i]) {
			case 0: e[i]=0; break;
			case 1: e[i] = 2; break;
			case -1: e[i] = -2; break;
			case 2: e[i] = 1; break;
			case -2: e[i]= -1; break;
			default: break;
			}
		}
		return e;
	}
	
	public int countCorrectCorners()
	{
		int c = 0;
		for (int xs = -1; xs < 2; xs += 2)
		{
			for (int ys = -1; ys < 2; ys += 2)
			{
				for (int zs = -1; zs < 2; zs += 2)
				{
					int[] signs = new int[] {xs,ys,zs};
					boolean flag = true;
					for (int i = 0; i < 3; i++) {
						
						int[] corner = new int[] {1,1,1};
						for (int j = 0; j < corner.length; j++) {
							corner[j] *= signs[j];
						}
						corner[i] *= 2;
						
						int[] center = new int[] {0,0,0};
						center[i] += 2*signs[i];
						if (!this.biasedGet(corner).equals(this.biasedGet(center)))
						{
							flag = false;
							break;
						}
						
					}
					if (flag)
						c++;
				}
				
			}
			
		}
		return c;
	}
	
	private Object biasedGet(int x, int y, int z)
	{
		return this._cube[x+2][y+2][z+2];
	}
	
	private Object biasedGet(int[] coords)
	{
		return this.biasedGet(coords[0],coords[1],coords[2]);
	}
	
	
	public static void main(String[] args) {
		Cube cube = new Cube();
		System.out.println(cube);
		System.out.println(cube.countCorrectEdges());
		cube.turn(EFace.UP, true);
		System.out.println(cube);
		System.out.println(cube.countCorrectCorners());
		System.out.println(cube.countCorrectEdges());
	}

	public void randomize(int n) {
		Random r = new Random();
		for (int i = 0; i < n; i++) {
			EMove[] moves = EMove.values();
			EMove move = moves[r.nextInt(moves.length)];
			this.turn(move.getFace(), move.isCCW());
		}
	}
}

